<html>
<head>
<title>ArrayBuffer External Memory test</title>
<script>

var log;
function print(message, color)
{
    var paragraph = document.createElement("div");
    paragraph.appendChild(document.createTextNode(message));
    paragraph.style.fontFamily = "monospace";
    if (color)
        paragraph.style.color = color;
    log.appendChild(paragraph);
}

function pass(msg)
{
    print("PASS: " + msg, "green");
}

function fail(msg)
{
    print("FAIL: " + msg, "red");
}

var KB = 1024;
var MB = KB * KB;
var noise = KB;

function externalMemory() {
    return getV8Statistics().amount_of_external_allocated_memory;
}

function collectGarbage() {
    for (var i = 0; i < 10; i++) gc();
}

function allocationsThatIncreaseExternalMemory() {
    function test(expression) {
        var before = externalMemory();
        (function () { eval(expression); }) ();
        var now = externalMemory();
        if (now < before + MB - noise) {
            fail(expression + " did not increase the amount of external memory (" +
                  before + ", " + now + ").");
        } else {
            pass(expression + " increased the amount of external memory.");
        }
        collectGarbage();
        var after = externalMemory();
        if (after > now + noise) {
            fail("Garbage collection after " + expression +
                 " did not return the amount of external memory to the initial value (" +
                 now + ", " + after + ").");
        } else {
            pass("Garbage collection after " + expression +
                 " returned the amount of external memory to the initial value.");
        }
    }

    test("(new ArrayBuffer(MB))");
    test("(new Float32Array(MB))");
    test("(new Float64Array(MB))");
    test("(new Int8Array(MB))");
    test("(new Int16Array(MB))");
    test("(new Int32Array(MB))");
    test("(new Uint8Array(MB))");
    test("(new Uint16Array(MB))");
    test("(new Uint32Array(MB))");
    test("(new BigInt64Array(MB))");
    test("(new BigUint64Array(MB))");
    var largeJSArray = [];
    for (var i = 0; i < MB; i++) largeJSArray.push(i);
    test("(new Float32Array(largeJSArray))");
    test("(new Float64Array(largeJSArray))");
    test("(new Int8Array(largeJSArray))");
    test("(new Int16Array(largeJSArray))");
    test("(new Int32Array(largeJSArray))");
    test("(new Uint8Array(largeJSArray))");
    test("(new Uint16Array(largeJSArray))");
    test("(new Uint32Array(largeJSArray))");
    test("(new BigInt64Array(largeJSArray, (x) => BigInt(x))");
    test("(new BigUint64Array(largeJSArray, (x) => BigInt(x))");
    var int8Array = new Int8Array(MB);
    test("(new  Float32Array(int8Array))");
    test("(new  Float64Array(int8Array))");
    test("(new  Int8Array(int8Array))");
    test("(new  Int16Array(int8Array))");
    test("(new  Int32Array(int8Array))");
    test("(new  Uint8Array(int8Array))");
    test("(new  Uint16Array(int8Array))");
    test("(new  Uint32Array(int8Array))");
    test("(new  BigInt64Array(int8Array, (x) => BigInt(x))");
    test("(new  BigUint64Array(int8Array, (x) => BigInt(x))");
}


function allocationsThatDoNotChangeExternalMemory() {
    function test(expression) {
        var before = externalMemory();
        (function () { eval(expression); }) ();
        var now = externalMemory();
        if (now > before + noise) {
            fail(expression + " increased the amount of external memory (" + before + ", " + now + ").");
        } else {
            pass(expression + " did not increase the amount of external memory.");
        }
        collectGarbage();
        var after = externalMemory();
        if (after < now - noise) {
            fail("Garbage collection after " + expression + " decreased the amount of external memory (" +
                 now + ", " + after + ").");
        } else {
            pass("Garbage collection after " + expression +
                 " did not decrease the amount of external memory.");
        }
    }
    var arrayBuffer = new ArrayBuffer(MB);
    test("(new  Float32Array(arrayBuffer))");
    test("(new  Float64Array(arrayBuffer))");
    test("(new  Int8Array(arrayBuffer))");
    test("(new  Int16Array(arrayBuffer))");
    test("(new  Int32Array(arrayBuffer))");
    test("(new  Uint8Array(arrayBuffer))");
    test("(new  Uint16Array(arrayBuffer))");
    test("(new  Uint32Array(arrayBuffer))");
    test("(new  BigInt64Array(arrayBuffer))");
    test("(new  BigUint64Array(arrayBuffer))");
    var int8Array = new Int8Array(MB);
    test("(new  Float32Array(int8Array.buffer))");
    test("(new  Float64Array(int8Array.buffer))");
    test("(new  Int8Array(int8Array.buffer))");
    test("(new  Int16Array(int8Array.buffer))");
    test("(new  Int32Array(int8Array.buffer))");
    test("(new  Uint8Array(int8Array.buffer))");
    test("(new  Uint16Array(int8Array.buffer))");
    test("(new  Uint32Array(int8Array.buffer))");
    test("(new  BigInt64Array(int8Array.buffer))");
    test("(new  BigUint64Array(int8Array.buffer))");
}


function transfersThatDecreaseExternalMemory() {
    var workerSource =
"function externalMemory() {\n" +
"    return getV8Statistics().amount_of_external_allocated_memory;\n" +
"}\n" +
"var KB = 1024;\n" +
"var MB = KB * KB;\n" +
"var noise = KB;\n" +
"self.onmessage = function(e) {\n" +
"    var before = externalMemory();\n" +
"    e.data;\n" +
"    var after = externalMemory();\n" +
"    if (after > before + MB - noise) {\n" +
"      self.postMessage('PASS: Amount of external memory increased.');\n" +
"    } else {\n" +
"      self.postMessage('FAIL: Amount of external memory did not increase.');\n" +
"    }\n" +
"}\n";

    var blob = new Blob([workerSource]);
    var worker = new Worker(window.webkitURL.createObjectURL(blob));
    worker.onmessage = function (e) {
        print("message from worker: " + e.data, "blue");
    }
    function test(expression)
    {
        var buffer = eval(expression);
        try {
            var before = externalMemory();
            worker.postMessage(buffer, [buffer]);
            var now = externalMemory();
            if (now > before - MB + noise) {
                fail("Transfer of " + expression + " did not decrease the amount of external memory (" +
                     before + ", " + now + ").");
            } else {
                pass("Transfer of " + expression + " decreased the amount of external memory.");
            }
            collectGarbage();
            var after = externalMemory();
            if (after < now - noise) {
                fail("Garbage collection after transfer of " + expression +
                     " decreased the amount of external memory (" + now + ", " + after + ").");
            } else {
                pass("Garbage collection after transfer of " + expression +
                     " did not decrease the amount of external memory.");
            }
        } catch (e) {
            fail("Transfer of " + name + ": could not webkitPostMessage: " + e);
            return false;
        }
        return true;
    }
    test("(new ArrayBuffer(MB))");
    test("(new Float32Array(MB)).buffer");
    test("(new Float64Array(MB)).buffer");
    test("(new Int8Array(MB)).buffer");
    test("(new Int16Array(MB)).buffer");
    test("(new Int32Array(MB)).buffer");
    test("(new Uint8Array(MB)).buffer");
    test("(new Uint16Array(MB)).buffer");
    test("(new Uint32Array(MB)).buffer");
    test("(new BigInt64Array(MB)).buffer");
    test("(new BigUint64Array(MB)).buffer");
}


function runAll() {
    log = document.getElementById("log1");
    if (typeof gc == "undefined" || typeof getV8Statistics == "undefined") {
        print("Run chrome browser with --js-flags='--expose_gc --track_gc_object_stats'", "red");
    } else {
         allocationsThatIncreaseExternalMemory();
         collectGarbage();
         allocationsThatDoNotChangeExternalMemory();
         collectGarbage();
         log = document.getElementById("log2");
         transfersThatDecreaseExternalMemory();
         collectGarbage();
    }
}

</script>
</head>
<body onload="runAll()">
<p>This test checks that allocation and deallocation of typed arrays correctly
adjusts the amount of external memory in V8.</p>
<div id='log1'></div>
<p>This test checks that transfer of an array buffer to worker decreases amount of
external memory in the main V8 isolate.</p>
<div id='log2'></div>
</body>
</html>
